我使用gemini提取出了老师视频中展示的文档,非常好。因为老师确实没有给文档,blog里面也搜不到,幸好有了AI,帮我很大的忙。
Install NodeJS and Database 前提是要安装nodejs和某种数据库,因为prisma通常是为关系型数据库构建的,所以NoSQL这些大部分都不能支持,只支持MongoDB。我按照老师的要求,安装了postgresql。
npm init -y ,初始化项目。
npm i --save-dev prisma typescript ts-node @types/node nodemon , Install dependencies
Create tsconfig.json
xxxxxxxxxx91{2 "compilerOptions": {3 "sourceMap": true,4 "outDir": "dist",5 "strict": true,6 "lib": ["esnext"],7 "esModuleInterop": true8 }9}这个是配置typescript相关的。
npx prisma init --datasource-provider postgresql , Initial Prisma project
Connect Database
a. Install VSCode extension for Prisma
b. Also talk about auto formatting and npx prisma format
xxxxxxxxxx131// schema.prisma2datasource db {3 provider = "postgresql"4 url = env("DATABASE_URL")5}67generator client {8 provider = "prisma-client-js"9 output = "../generated/prisma"10}1112// .env13DATABASE_URL="postgresql://postgres:password@localhost:5433/test"这里需要重点讲解一下,datasource db指的是Prisma orm该怎么样连接到数据库管理系统,provider指的是数据库管理系统的类型,可以是mysql、sqlite等等,url指的是连接数据库管理系统的哪一个数据库。
在步骤4执行之后,会生成schema.prisma文件和.env文件,在.env文件里面,有一个DATABASE_URL的变量,这里存放的是数据库的地址。这个地址可以是本地数据库的地址,也可以是云服务器中数据库的地址。

generator的作用是定义代码生成规则,provider指定用哪一个生成器,output自定义生成文件的输出目录。因为prisma的核心是“基于schema自动生成代码”,而generator就是“告诉prisma”要生成什么,怎么生成,生成到哪里“的关键配置。
只有这样配置了,在代码中才能使用PrismaClient,来方便的操作数据库。
url的格式是:postgresql://用户名:密码@主机:端口/数据库名,我的本地用户名默认是postgres,密码是123456,主机是localhost,端口是5432,数据库需要新建,打开powershell,输入psql -h localhost -p 5432 -U postgres,输入密码123456,然后输入create database prisma,这样就生成了一个名称为prisma的数据库。
那么这里的url就写成这样DATABASE_URL="postgresql://postgres:123456@localhost:5432/prisma?schema=public",这里的?schema=public,它的意思是告诉 Prisma(或其他工具):默认使用 public 这个 schema 来查找表和执行查询。这涉及到postgresql的表结构,和MySQL是不一样的,先不用管。


Create User Schema with name
xxxxxxxxxx41model User {2 id Int @id @default(autoincrement())3 name String4}npx prisma migrate dev --name init , Create/Run migration。这一步是为了将定义的model迁移到数据库中去,dev表示只在开发环境做这件事,--name init表示为这次操作设置名称,方便以后查看。
这一步执行之后,会在prisma文件夹里面生成一个migrations文件夹,里面就是迁移文件,这些文件将与postgresql交互并进行用户指定的修改。
与此同时,也对Prisma client进行了更新,这样使用prisma client的时候,就能够用到最新的数据库。

npm install @prisma/client - Install Client (Talk about how this also generates the client as well for us and how this generation happens every time you migrate as well and how you can create this generation your self with npx prisma generate)
创建一个script.ts文件,就可以写代码了。
xxxxxxxxxx151import { PrismaClient } from '@prisma/client'23const prisma = new PrismaClient({ log: ["query"] })45async function main() {6 // ... you will write your Prisma Client queries here7}89main()10 .catch((e) => {11 console.error(e.message)12 })13 .finally(async () => {14 await prisma.$disconnect()15 })await prisma.user.create({ data: { name: "Sally" } }) - Create new user
await prisma.user.findMany() - Get all users
Must have at least one generator but can have more than one
The provider represents what generator to use
a. This can be any NPM library such as a GraphQL generator
你想要的 PostgreSQL 类型 Prisma 写法(强烈推荐) 说明(精度 / 时区) 推荐指数 timestamp with time zone(最推荐) DateTime @db.Timestamptz 保存 UTC 时间,带时区无关,精度 6 位(微秒) 5 stars timestamp without time zone DateTime @db.Timestamp 保存本地时间,不带时区,容易踩坑 2 stars timestamptz(简写,同第 1 种) DateTime(不写任何 @db 也行) Prisma 默认就是 timestamptz! 5 stars date(只存日期,不带时间) DateTime @db.Date 相当于 2025-12-05,不带时间和时区 4 stars time(只存时间,不带日期) DateTime @db.Time 13:14:15,精度到微秒 3 stars timetz(带时区的时间) DateTime @db.TimeTz 很少用 1 star xxxxxxxxxx121model User {2id String @id @default(cuid())34createdAt DateTime @default(now()) // 自动就是 timestamptz5updatedAt DateTime @updatedAt // 自动就是 timestamptz67birthday DateTime @db.Date // 只存年月日,比如 1998-08-088lastLogin DateTime? // 可空,带时区的时间戳910// 如果你真的想明确写出来(可读性更好)11loginAt DateTime @db.Timestamptz12}
Post)基本语法:
xxxxxxxxxx11@relation(name?: String, fields?: String[], references?: String[], onDelete?: ReferentialAction, onUpdate?: ReferentialAction, map?: String)最重要的三个参数:
参数 类型 必填? 放在哪一边? 说明与取值 name String(字符串字面量) 选填(但强烈建议写上) 两边都要写(且相同) 关系名字,用于消除歧义;迁移时保持关系稳定;精准include/select。 fields String[](字段名数组) 必须(有外键的一方) 外键所在的一方(多的一方) 指明本模型里哪个字段是外键 references String[](字段名数组) 必须(有外键的一方) 外键所在的一方 指向对端模型的哪个字段(通常是 @id 或 @@id) 这里的name可以用于精准include/select,这种需求在项目中是很常见的:
在多对多关系中,指定name参数,prisma会用这个name为我们创建一个中间表。
多对多关系的
@relation指令不需要显式地定义fields和references参数。xxxxxxxxxx111model Student {2id Int @id @default(autoincrement())3name String4courses Course[] @relation("Enrollment")5}67model Course {8id Int @id @default(autoincrement())9title String10students Student[] @relation("Enrollment")11}解释:
@relation("Enrollment")中的name是 "Enrollment"。这意味着 Prisma 会为Student和Course之间的多对多关系创建一个中间表,名称会是Enrollment。- 通过使用
name,我们可以明确中间表的名称,而不依赖 Prisma 默认的命名规则。查询:
xxxxxxxxxx51const students = await prisma.student.findMany({2include: {3courses: true, // 通过 include 加载关联的课程4},5})xxxxxxxxxx251model User {2id String @id @default(cuid())34// 我写的文章(一对多)5authoredPosts Post[] @relation("AuthoredPosts")67// 我点赞的文章(隐式多对多)← 两边 name 完全一样8likedPosts Post[] @relation("UserLikedPosts")910createdAt DateTime @default(now())11}1213model Post {14id String @id @default(cuid())15title String1617// 作者(一对多)18authorId String19author User @relation("AuthoredPosts", fields: [authorId], references: [id])2021// 被谁点赞(隐式多对多)← 两边 name 完全一样,不能写 fields/references22likedBy User[] @relation("UserLikedPosts")2324@@index([authorId])25}xxxxxxxxxx181const user = await prisma.user.findUnique({2where: { id: "clxxxxxx" }, // 必须加 where!3include: {4authoredPosts: true, // 我写的5likedPosts: true, // 我点的赞6},7})89// 或者分开查10const written = await prisma.user.findUnique({11where: { id },12select: { authoredPosts: { take: 10 } }13})1415const liked = await prisma.user.findUnique({16where: { id },17select: { likedPosts: { take: 20 } }18})如果两个关系都没写 name,include 里只能写 posts,要么全拿,要么全不拿,完全无法分开。
一、什么时候可以不写 @relation?(最常见的情况)
xxxxxxxxxx101model User {2id String @id @default(cuid())3posts Post[] // 这一边啥也不写4}56model Post {7id String @id @default(cuid())8author User @relation(fields: [authorId], references: [id])9authorId String10}结论:只要关系是「有外键的一方」写了 fields 和 references,另一方(User)可以完全不写 @relation,Prisma 自动推断,100% 正常工作。 这是目前 90% 项目都在用的最简写法。
二、什么时候必须写 @relation?(4 种硬性场景)
场景 必须写 @relation 的原因 正确写法示例 1. 自关联(同一个表里关联自己) 不写会报错 “Ambiguous relation” 见例子1 2. 一对多双向,但两边外键字段名不一致 Prisma 无法自动匹配 见例子2 3. 一个模型被多个关系引用(多对一多条) Prisma 不知道 Post[] 对应哪条关系 见例子3 4. 多对多显式关系(中间表) 必须写 name 来配对 三、四大场景详细写法
场景1:自关联(粉丝、关注、上下级、树形评论)
xxxxxxxxxx131model User {2id String @id @default(cuid()34// 我关注的(我 → 对方)5following User[] @relation("FollowRelation", fields: [followingId], references: [id])6followingId String?78// 关注我的(对方 → 我)9followers User[] @relation("FollowRelation")1011// 推荐:加索引12@@index([followingId])13}关键:两边都用同一个名字 "FollowRelation",不然会报错。
场景2:外键字段名不是默认的 authorId
xxxxxxxxxx111model Post {2id String @id @default(cuid())3title String4creator User @relation(fields: [createdById], references: [id])5createdById String // ← 不是 authorId!6}78model User {9id String @id @default(cuid())10posts Post[] @relation("CreatedPosts") // 必须写 name,否则找不到11}场景3:一个模型被多个关系引用(最容易踩坑!)
xxxxxxxxxx151model User {2id String @id @default(cuid())34writtenPosts Post[] @relation("Author") // 我写的文章5likedPosts Post[] @relation("Likes") // 我点赞的文章6}78model Post {9id String @id @default(cuid())1011author User @relation("Author", fields: [authorId], references: [id])12authorId String1314likedBy User[] @relation("Likes")15}不写 name 的话 Prisma 会报:Error: Ambiguous relation on model User.posts
场景4:显式多对多(中间表自己控制)
xxxxxxxxxx171model User {2id String @id @default(cuid())3followedBy Follow[] // 我被谁关注4following Follow[] // 我关注了谁5}67model Follow {8followerId String9followingId String1011follower User @relation("Following", fields: [followerId], references: [id])12following User @relation("FollowedBy", fields: [followingId], references: [id])1314followedAt DateTime @default(now())1516@@id([followerId, followingId]) // 复合主键17}
符号就下面两个,另外还有两个方法。
[]
a. Turns the column into a list. When used on a reference type it creates all needed foreign keys and such
b. Can only be used on scalar types if the DB supports it
?
a. Makes a field optional
b. Cannot be used with []
| 修饰符 | 写法位置 | 含义 | 数据库表现(PostgreSQL) | 常用场景 |
|---|---|---|---|---|
| ? | 放在类型后面 | 可选字段(可为 null) | nullable = true | 头像、昵称、手机号、描述等非必填字段 |
| [] | 放在类型后面 | 数组(List) | text[]、int4[]、timestamptz[] 等 | 标签、角色列表、图片数组、爱好等 |
| @default() | 字段属性 | 默认值 | DEFAULT ... | 状态、积分、排序、是否启用等 |
| @updatedAt | 字段属性 | 自动更新为当前时间 | ON UPDATE NOW() | 任何需要记录最后修改时间的字段 |
Attributes have two levels
a. Field attributes apply to one field @
b. Block attributes apply to the entire block @@
@id
a. All tables must have an id field or have a unique field
b. Can be combined with @default
i. autoincrement()-这个在postgresql里面就是serial类型了。
ii. uuid()
iii. cuid()-prisma推荐使用的id,就用这个
@@id
a. Used to create composite ids @@id([FirstName, LastName])
@default
a. Can be passed a static value or a function such as now()
@unique - Makes a field unique
@@unique - Makes a combination of fields unique @@unique(authorId, postId)
@@index - Create index on one or more fields @@index(authorId, postId)
@updatedAt - Automatically sets a field to the current time when you update the row
@relation
a. The fields with relation types are not actually in the db
b. One To Many
xxxxxxxxxx101model User {2 id Int @id @default(autoincrement())3 posts Post[]4}56model Post {7 id Int @id @default(autoincrement())8 author User @relation(fields: [authorId], references: [id])9 authorId Int10}c. One To One
xxxxxxxxxx101model User {2 id Int @id @default(autoincrement())3 profile Profile?4}56model Profile {7 id Int @id @default(autoincrement())8 user User @relation(fields: [userId], references: [id])9 userId Int @unique10}d. Many To Many
xxxxxxxxxx91model Post {2id Int @id @default(autoincrement())3categories Category[]4}56model Category {7id Int @id @default(autoincrement())8posts Post[]9}
e. The @relation field must take a field and references value.
i. The field points to the field on the current model
ii. The references points to the field on the other model the field value references
f. If you have multiple relations on the same model you need to pass a name to the references attribute to disambiguate them
xxxxxxxxxx131model User {2 id Int @id @default(autoincrement())3 writtenPosts Post[] @relation("WrittenPosts")4 favoritePosts Post[] @relation("FavoritePosts")5}67model Post {8 id Int @id @default(autoincrement())9 author User @relation("WrittenPosts", fields: [authorId], references: [id])10 authorId Int11 favoritedBy User? @relation("FavoritePosts", fields: [favoritedById], references: [id])12 favoritedById Int?13}完全列表(官方所有字段属性,一共就这些)
| 分类 | 属性名称 | 示例 |
|---|---|---|
| 主键 | @id, @@id([]) | |
| 唯一约束 | @unique, @@unique([]) | |
| 默认值 | @default(), @updatedAt | |
| 数据库映射 | @map(), @@map() | |
| 数据库类型 | @db.Xxx(所有 PostgreSQL 类型都支持) | @db.Timestamptz, @db.Uuid, @db.VarChar(100) |
| 索引 | @@index([]), @@index([], type: Xxx) | GIN / Hash / Brin / Gist |
| 全文搜索 | @@fulltext([]) | |
| 关系 | @relation() | |
| 忽略字段 | @ignore | |
| 原生默认 | @default(dbgenerated("...")) |
举例说明:
| 频率 | 属性写法 | 作用 | 典型值 / 示例 | 必背程度 |
|---|---|---|---|---|
| 1 | @id | 声明主键(单字段) | @id | 5 stars |
| 2 | @@id([field1, field2]) | 复合主键 | @@id([tenantId, id]) | 5 stars |
| 3 | @unique | 单字段唯一约束 | email String @unique | 5 stars |
| 4 | @@unique([field1, field2]) | 复合唯一约束 | @@unique([tenantId, email]) | 5 stars |
| 5 | @default(value) | 默认值 | @default(cuid())、@default(now())、@default(0)、@default(true)、@default([])、@default("{}") | 5 stars |
| 6 | @updatedAt | 自动维护更新时间 | updatedAt DateTime @updatedAt | 5 stars |
| 7 | @relation(...) | 关系字段(前面已详细讲) | 见前文 | 5 stars |
| 8 | @map("column_name") | 自定义数据库列名 | username String @map("user_name") | 4 stars |
| 9 | @@map("table_name") | 自定义表名 | @@map("users" | 4 stars |
| 10 | @db.Type | 强制指定 PostgreSQL 底层类型 | @db.Timestamptz、@db.VarChar(50)、@db.JsonB、@db.Date、@db.Uuid | 4 stars |
| 11 | @@index([field]) | 单字段索引 | @@index([email]) | 4 stars |
| 12 | @@index([field1, field2]) | 复合索引 | @@index([tenantId, createdAt]) | 4 stars |
| 13 | @@index([field], type: Gin) | GIN 索引(用于数组、jsonb、全文搜索) | @@index([tags], type: Gin)、@@index([metadata], type: Gin) | 4 stars |
| 14 | @@index([field], type: Hash) | Hash 索引(只适合 = 查询) | @@index([status], type: Hash) | 2 stars |
| 15 | @@index([field], type: Brin) | BRIN 索引(超大表时间序列) | @@index([createdAt], type: Brin) | 2 stars |
| 16 | @ignore | 完全忽略这个字段(不映射到数据库) | @ignore(极少用) | 1 star |
| 17 | @default(dbgenerated("sql")) | 使用数据库原生默认表达式 | @default(dbgenerated("gen_random_uuid()")) | 3 stars |
| 18 | @@fulltext([title, content]) | PostgreSQL 原生全文搜索索引 | @@fulltext([title, content]) | 3 stars |
如果使用的是postgresql,推荐使用postgresql原生enums,就是在postgresql数据库里面定义enums。然后在model定义的时候使用即可。
不推荐使用prisma的方式来定义enums,就是在schema.prisma里面不要定义enums。可以搜一下为什么,这里给一个理由:给已有枚举加一个新状态 → Prisma Migrate 直接 drop + recreate 整个 enum 类型,后果就是依赖这个 enum 的所有表都要重建 → 几千万行的大表锁表 40 分钟~3 小时,业务直接宕机。
而在postgresql原生里面进行修改,几乎没有什么影响,这是实际经验教训。
xxxxxxxxxx91model User {2 id Int @id @default(autoincrement())3 role Role @default(BASIC)4}56enum Role {7 BASIC8 ADMIN9}
xxxxxxxxxx571// This is your Prisma schema file,2// learn more about it in the docs: https://pris.ly/d/prisma-schema34generator client {5 provider = "prisma-client-js"6}78datasource db {9 provider = "sqlite"10 url = env("DATABASE_URL")11}1213model User {14 id String @id @default(uuid())15 isAdmin Boolean @default(false)16 age Int17 name String18 email String @unique19 writtenPosts Post[] @relation("WrittenPosts")20 favoritePosts Post[] @relation("FavoritePosts")21 preferences UserPreference? @relation(fields: [userPreferenceId], references: [id])22 role Role @default(BASIC)23 userPreferenceId String? @unique2425 @@unique([name, age])26 @@index([email])27}2829model UserPreference {30 id String @id @default(uuid())31 emailUpdates Boolean32 user User?33}3435model Post {36 id String @id @default(uuid())37 title String38 averageRating Float // Also talk about BigInt, Decimal, Json, Unsupported, an39 createdAt DateTime @default(now())40 updatedAt DateTime @updatedAt41 author User @relation("WrittenPosts", fields: [authorId], references: [id])42 authorId String43 favoritedBy User? @relation("FavoritePosts", fields: [favoritedById], references: [id])44 favoritedById String?45 categories Category[]46}4748model Category {49 id String @id @default(uuid())50 name String @unique51 posts Post[]52}5354enum Role {55 BASIC56 ADMIN57}先记住下面最简单的操作,复杂操作查询文档即可,不要连最简单的操作都记不住。
create
xxxxxxxxxx71const user = await prisma.user.create({2 data: {3 email: 'kyle@test.com',4 name: 'Kyle',5 age: 276 }7})一对一、多对一新增使用connect来关联。
xxxxxxxxxx111// Create a new Post record and connect it to an existing User record.创建新post,同时将这个post关联到user.2// 注意,connect后面跟的只能是标了 @unique 或参与了 @@unique / @@id / @id 的字段,不一定只能是主键。34const user = await prisma.post.create({5 data: {6 title: 'Hello World',7 author: {8 connect: { email: 'alice@prisma.io' },9 },10 },11});createMany
x
1const user = await prisma.user.createMany({2 data: [3 {4 email: 'sally@test.com',5 name: 'Sally',6 age: 257 },8 {9 email: 'john@test.com',10 name: 'John',11 age: 1212 }13 ],14 skipDuplicates: true, // @unique 字段会校验是否唯一15})就记住这三个,不要嫌少,用的最多的就是这三种方法。
findUnique - Can also include/select
按 id、@unique 字段或 @@unique / @@id 组合查一条记录;
x
1241// 1. 按主键查2prisma.user.findUnique({ where: { id: userId } })34// 2. 按任意 @unique 字段查(超好用!)5prisma.user.findUnique({ where: { email } }) // email @unique6prisma.user.findUnique({ where: { phone: "13800138000" } })7prisma.user.findUnique({ where: { slug: "alice" } })89// 3. 复合唯一索引(多租户必备),schema里面是这样定义的 @@unique([tenantId, email], name: "tenant_email")10prisma.user.findUnique({11 where: { tenantId_email: { tenantId: "t1", email: "test@163.com" } }12})1314// 4. 复合主键(如果你的表是 @@id([a, b]))15prisma.order.findUnique({16 where: { tenantId_orderNo: { tenantId: "t1", orderNo: "20250001" } }17})findFirst - Same as findMany but only gets first result
条件不是 schema 中的唯一键(例如多字段组合但没加 @@unique,或复杂过滤);
xxxxxxxxxx41prisma.post.findFirst({2 where: { published: true, author: { email: { contains: "gmail" } } },3 orderBy: { createdAt: 'desc' },4})findMany - Can also select/include
a. distinct - Can do distinct queries
b. take / skip - Can do pagination
c. orderBy - Can do sorting
x
1const results = await prisma.post.findMany({2 where: {3 title: { contains: 'Prisma' },4 },5 orderBy: { id: 'asc' },6})where条件:
xxxxxxxxxx761// 2.2 where 条件全家福(标量 + 数组 + JSONB + 关系)2prisma.post.findMany({3 where: {4 // 数值比较运算符(全部支持)5 viewCount: { 6 gt: 1000, // 大于 10007 gte: 1000, // 大于等于8 lt: 10000, // 小于9 lte: 10000, // 小于等于10 equals: 5000, // 等于(可以省略,直接写 viewCount: 5000 也行)11 not: 0, // 不等于12 },1314 // 字符串操作(底层走 PostgreSQL ILIKE / LIKE)15 title: { 16 contains: "Prisma", // 包含「Prisma」17 mode: "insensitive" // 不区分大小写(默认就是 insensitive,写出来更明确)18 },19 title: { startsWith: "2025" }, // 以「2025」开头20 title: { endsWith: ".md" }, // 以「.md」结尾21 title: { in: ["置顶", "精华"] }, // IN 数组22 title: { notIn: ["广告"] },2324 // 数组字段(String[] / Int[] 等)25 tags: { 26 has: "TypeScript", // 数组里包含某个值27 hasEvery: ["JS", "TS"], // 必须同时包含这几个28 hasSome: ["Vue", "React"], // 包含其中任意一个即可29 isEmpty: true, // 空数组30 },3132 // JSONB 字段操作(超级实用)33 metadata: { 34 equals: { plan: "pro" }, // 完全相等35 path: ["plan"], equals: "pro" // metadata->>'plan' = 'pro'36 path: ["settings", "theme"], equals: "dark", // 嵌套路径37 string_contains: "vip", // JSON 字符串里包含 vip38 string_starts_with: "gold", // 以 gold 开头39 string_ends_with: "member",40 },4142 // 关系存在性查询(神技!不需要 join)43 author: { 44 is: { isVip: true } , // 作者是 VIP45 isNot: { status: "banned" }, // 作者不是被封禁的46 },47 comments: { 48 some: { content: { contains: "赞" } }, // 至少有一条评论含「赞」49 none: { authorId: userId }, // 这篇文章没人评论过(我没评论)50 every: { content: { not: { contains: "差评" } } }, // 所有评论都不含差评51 },52 likedBy: { some: { id: userId } }, // 我点过赞的文章5354 // 逻辑组合55 AND: [56 { published: true },57 { createdAt: { gte: new Date("2025-01-01") } },58 ],59 OR: [60 { title: { contains: "Prisma" } },61 { content: { contains: "ORM" } },62 ],63 NOT: { status: "deleted" },64 },6566 // 排序(支持多字段)67 orderBy: [68 { viewCount: 'desc' }, // 阅读量倒序69 { createdAt: 'asc' }, // 时间正序(同分再按时间)70 ],7172 // 分页方式推荐(游标分页 > offset)73 take: 20,74 skip: 0,75 // cursor: { id: "last_seen_id" }, // 游标分页示例76});include/select方式:
521// 2.3 include vs select 详细注释版2prisma.user.findUnique({3 where: { id: userId },45 // include 方式:全部字段 + 关联(简单粗暴,适合快速开发)6 include: {7 posts: {8 where: { published: true },9 orderBy: { createdAt: 'desc' },10 take: 10,11 include: {12 tags: true, // 标签全量13 _count: { select: { comments: true, likedBy: true } }, // 评论数、点赞数14 },15 },16 profile: true, // 一对一 profile 全量17 _count: { // 统计关联数量18 select: {19 posts: true, // 文章总数20 likedPosts: true, // 点过赞的文章数21 },22 },23 },2425 // select 方式:精准控制字段(生产环境强烈推荐!性能更好)26 select: {27 id: true,28 email: true,29 name: true,30 createdAt: true,3132 posts: {33 select: {34 id: true,35 title: true,36 createdAt: true,37 _count: { select: { comments: true } },38 },39 where: { published: true },40 orderBy: { createdAt: 'desc' },41 take: 5,42 },4344 profile: {45 select: { bio: true },46 },4748 _count: {49 select: { posts: true, likedPosts: true },50 },51 },52});聚合、统计、分组:
xxxxxxxxxx291// 2.4 聚合、统计、分组(2025 年最常用写法)2const result = await prisma.post.aggregate({3 where: { authorId: userId },4 _count: { id: true }, // 文章总数5 _avg: { viewCount: true }, // 平均阅读量6 _sum: { viewCount: true }, // 总阅读总量7 _min: { viewCount: true },8 _max: { viewCount: true },9});1011const group = await prisma.post.groupBy({12 by: ['authorId'], // 按作者分组13 _count: { id: true }, // 每人文章数14 _avg: { viewCount: true },15 having: {16 id: { _count: { gt: 5 } }, // 只返回文章数 >5 的作者17 },18 orderBy: { _count: { id: 'desc' } },19});2021const count = await prisma.post.count({22 where: {23 published: true,24 AND: [25 { createdAt: { gte: new Date("2025-01-01") } },26 { createdAt: { lte: new Date("2025-12-31") } },27 ],28 },29});简单案例:
x
1const result = await prisma.user.findMany({2 where: {3 post: {4 every: {5 published: true6 },7 some: {8 content: {9 contains: "Prisma"10 }11 }12 }13 }14})xxxxxxxxxx631prisma.post.findMany({2 where: {3 // 1. some:关联记录中「至少有一条」满足条件(最常用!)4 comments: {5 some: {6 content: { contains: "厉害" }, // 有评论说“厉害”7 // OR: [{ author: { isVip: true } }, { content: { contains: "1000+" } }]8 },9 },1011 likedBy: {12 some: { id: currentUserId }, // 我点过赞的文章13 },1415 tags: {16 some: { name: { in: ["TypeScript", "Prisma"] } }, // 至少有一个标签是 TS 或 Prisma17 },1819 // 2. every:关联记录「全部」都要满足条件(常用于审核/权限)20 comments: {21 every: {22 status: "approved", // 所有评论都已通过审核(没有一条待审或拒绝)23 },24 },2526 // 3. none:关联记录「一条都不」满足条件 = 完全没有(神技!)27 comments: {28 none: { authorId: currentUserId }, // 我一条评论都没发过29 },3031 likedBy: {32 none: { id: currentUserId }, // 我没点过赞(可用于“显示点赞按钮”)33 },3435 reports: {36 none: { status: "pending" }, // 这篇文章没有任何待处理的举报 → 可以正常展示37 },3839 // 4. is:对端记录「本身」满足条件(一对多/一对一常用)40 author: {41 is: {42 isVip: true, // 作者是 VIP43 status: "active", // 作者状态正常44 // AND: [{ isVip: true }, { createdAt: { gte: new Date("2025-01-01") } }],45 },46 },4748 profile: {49 is: { bio: { not: null } }, // 一对一:用户填了 bio50 },5152 // 5. isNot:对端记录「本身」不满足条件53 author: {54 isNot: {55 status: "banned", // 作者没被封号56 // OR: [{ status: "deleted" }, { isVip: false }]57 },58 },59 },60 // 下面这些可以随便混着用61 orderBy: { createdAt: 'desc' },62 take: 20,63});| 关键词 | 含义 | 对应 SQL 感觉 |
|---|---|---|
| some | 至少一条 | EXISTS / IN |
| every | 全部满足 | NOT EXISTS(不满足的) |
| none | 一条都不 | NOT EXISTS |
| is | 对端本身满足 | 内层 WHERE |
| isNot | 对端本身不满足 | 内层 WHERE + NOT |
总体原则是先使用where条件,然后使用data更新值。
update - Can also include/select
a. Only updates 1 record
b. Must include data and where
①基础更新(单表)
xxxxxxxxxx141// 普通字段更新2await prisma.user.update({3 where: { id: userId }, // 任何唯一约束都行4 data: {5 name: "新名字",6 avatar: "https://xxx.jpg",7 status: "active",8 score: { increment: 10 }, // +109 viewCount: { decrement: 1 }, // -110 roles: { set: ["user", "admin"] }, // 完全替换数组11 roles: { push: "moderator" }, // 追加一个12 metadata: { set: { plan: "pro" } }, // JSON 整体替换13 },14});②一对多:修改文章标题 + 同时关联/取消关联作者
xxxxxxxxxx171await prisma.post.update({2 where: { id: postId },3 data: {4 title: "2025 年 Prisma 还是香",5 content: "更新了内容",67 // 更换作者(connect)8 author: { connect: { email: "new@author.com" } },910 // 解除作者关系(disconnect)11 // author: { disconnect: true },1213 // 置为 null(如果外键可空)14 // author: { disconnect: true },15 // authorId: null,16 },17});③更复杂的操作:多对多、upsert方法,用的时候查询即可。
数字操作,感觉不实用。
increment
decrement
multiply
divide
xxxxxxxxxx81const updatedUser = await prisma.post.update({2 where: { "name": "Kyle" },3 data: {4 age: {5 increment: 16 }7 }8})delete - Can also select/include
a. Only deletes 1 record
x
1await prisma.post.delete({2 where: { id: postId }, // 任何唯一约束都行3});deleteMany - Can also select/include
a. Only deletes 1 record
x
1await prisma.post.deleteMany({2 where: {3 authorId: userId,4 createdAt: { lt: new Date("2023-01-01") }, // 删除用户 2023 年前的文章5 },6});@ 或 @@ 开头属性Prisma 中所有以 @ 或 @@ 开头的东西都叫 Attributes(属性),可以作用于:
@@@下面给你一个 最完整、最清晰、业务开发最常用的列表。(已按用途分类,不用背,查表即可)
@ 开头用于 字段(column)级别 的功能,比如主键、默认值、映射等。
| 属性 | 作用 |
|---|---|
@id | 将字段设置为主键 |
@unique | 设置字段为唯一索引 |
@@id([]) | 复合主键(表级)→ 用 @@,但属于 ID 类 |
| 属性 | 作用 |
|---|---|
@default(value) | 字段默认值,如字符串、数字、布尔、函数 |
@default(now()) | 时间戳默认值 |
@default(uuid()) | 生成 UUID |
@default(cuid()) | 生成 cuid(prisma规范,强烈推荐使用) |
@updatedAt | 每次更新记录时自动更新该字段 |
| 属性 | 作用 |
|---|---|
@relation() | 定义关系,指定 foreign key 字段和引用字段 |
例:
xxxxxxxxxx11author User @relation(fields: [authorId], references: [id])| 属性 | 作用 |
|---|---|
@map("column_name") | 把 Prisma 字段映射到数据库列名 |
@@map("table_name") | 映射模型名到数据库表(表级) |
| 属性 | 作用 |
|---|---|
@db.VarChar(255) | 指定数据库实际类型(适配 PostgreSQL, SQLite, MySQL 等) |
@@ 开头用于 模型(表)级别 的功能,比如复合索引、表名映射等。
| 属性 | 作用 |
|---|---|
@@map("table_name") | 映射 Prisma 模型到数据库表 |
| 属性 | 作用 |
|---|---|
@@index([field1, field2]) | 普通索引 |
@@unique([field1, field2]) | 复合唯一索引 |
| 属性 | 作用 |
|---|---|
@@id([field1, field2]) | 设置复合主键 |
注意:复合主键不能和
@id共存。
一般用于双向关系中的自引用模型:
xxxxxxxxxx21parent Comment? @relation("CommentReplies")2replies Comment[] @relation("CommentReplies")虽然写在 @relation 里,但属于模型关系配置范畴。
@)| 属性 | 用途 |
|---|---|
@id | 主键 |
@unique | 唯一约束 |
@default() | 默认值 |
@updatedAt | 自动更新时间 |
@relation() | 外键、关系 |
@map() | 字段映射到数据库列 |
@db.* | 数据库原生类型 |
@@)| 属性 | 用途 |
|---|---|
@@map() | 模型映射到数据库表 |
@@index() | 索引 |
@@unique() | 复合唯一 |
@@id() | 复合主键 |